import {BaseEventArgs} from "./BaseEventArgs";
import {MultiDictionary} from "../../../Utils/MultiDictionary";
import Queue from "../../../Utils/Queue";
import {Event} from "./Event";
import LinkListNode from "../../../Utils/LinkList";
import {EventPoolMode} from "./EventPoolMode";
import {ReferencePool} from "../ReferencePool/ReferencePool";
export class EventPool<T extends BaseEventArgs>  {
    private readonly _eventHandlers:MultiDictionary<number, (sender,e:T)=>void>;
    private readonly _events:Queue<Event<T>>;
    private readonly _cachedNodes:Map<any,LinkListNode<(sender,e:T)=>void>>;
    private readonly _tempNodes:Map<any,LinkListNode<(sender,e:T)=>void>>;
    private readonly _eventPoolMode:EventPoolMode;
    private _defaultHandler:(sender,e:T)=>void;

    constructor(mode:EventPoolMode) {
        this._eventHandlers = new MultiDictionary();
        this._events = new Queue();
        this._cachedNodes = new Map<any, LinkListNode<(sender,e:T)=>void>>();
        this._tempNodes = new Map<any, LinkListNode<(sender,e:T)=>void>>();
        this._eventPoolMode = mode;
        this._defaultHandler = null;
    }

    get EventHandlerCount():number{
        return this._eventHandlers.Count;
    }

    get EventCount():number{
        return this._events.Size();
    }

    Update(elapseSeconds:number,realElapseSeconds:number):void{
        while (this._events.Size() > 0)
        {
            let eventNode:Event<T> = null;
            eventNode = this._events.dequeue();
            this.HandleEvent(eventNode.sender, eventNode.eventArgs);
            ReferencePool.Release(eventNode);
        }
    }

    Shutdown():void{
        this.Clear();
        this._eventHandlers.Clear();
        this._cachedNodes.clear();
        this._tempNodes.clear();
        this._defaultHandler = null;
    }

    Clear():void{
        this._events.Clear();
    }

    Count(id:number):number{
        let range = this._eventHandlers.get(id);
        if(range){
            return range.Count;
        }
        return 0;
    }

    Check(id:number,handler:(sender,e:T)=>void):boolean
    {
        if (handler == null)
        {
            throw new Error("Event handler is invalid.");
        }

        return this._eventHandlers.Contains(id, handler);
    }

    Subscribe(id:number,handler:(sender,e:T)=>void):void{
        if (handler == null)
        {
            throw new Error("Event handler is invalid.");
        }

        if (!this._eventHandlers.Contains(id))
        {
            this._eventHandlers.Add(id, handler);
        }
        else if ((this._eventPoolMode & EventPoolMode.AllowMultiHandler) != EventPoolMode.AllowMultiHandler)
        {
            throw new Error("Event '"+id+"' not allow multi handler.");
        }
        else if ((this._eventPoolMode & EventPoolMode.AllowDuplicateHandler) != EventPoolMode.AllowDuplicateHandler && this.Check(id, handler))
        {
            throw new Error("Event '"+id+"' not allow duplicate handler.");
        }
        else
        {
            this._eventHandlers.Add(id, handler);
        }
    }

    Unsubscribe(id:number,handler:(sender,e:T)=>void):void{
        if (handler == null)
        {
            throw new Error("Event handler is invalid.");
        }

        if (this._cachedNodes.size > 0)
        {
            this._cachedNodes.forEach((value,key) => {
                if(value.Value != null && value.Value == handler){
                    this._tempNodes.set(key,value.Next);
                }
            })



            if (this._tempNodes.size > 0)
            {
                this._tempNodes.forEach((value, key) => {
                    let cachedNode = this._cachedNodes.get(key);
                    cachedNode = value;
                })
                this._tempNodes.clear();
            }
        }

        if (!this._eventHandlers.Remove(id, handler))
        {
            throw new Error("Event '"+id+"' not exists specified handler.");
        }
    }

    SetDefaultHandler(handler:(sender,e:T)=>void):void{
        this._defaultHandler = handler;
    }

    Fire(sender,e:T):void{
        if (e == null)
        {
            throw new Error("Event is invalid.");
        }
        let eventNode:Event<T> = Event.Create(sender, e);
        this._events.enqueue(eventNode);
    }

    FireNow(sender,e:T):void{
        if (e == null)
        {
            throw new Error("Event is invalid.");
        }
        this.HandleEvent(sender, e);
    }

    private HandleEvent(sender,e:T):void{
        let noHandlerException:boolean = false;
        let range = this._eventHandlers.get(e.Id());
        if (range)
        {
            let current = range.first;
            while (current != null && current != range.terminal)
            {
                let cachedNode = this._cachedNodes.get(e);
                cachedNode = current.Next != range.terminal ? current.Next : null;
                current.Value(sender, e);
                current = cachedNode;
            }

            this._cachedNodes.delete(e);
        }
    else if (this._defaultHandler != null)
        {
            this._defaultHandler(sender, e);
        }
        else if ((this._eventPoolMode & EventPoolMode.AllowNoHandler) == 0)
        {
            noHandlerException = true;
        }

        ReferencePool.Release(e);

        if (noHandlerException)
        {
            throw new Error("Event '"+e.Id()+"' not allow no handler.");
        }
    }


}